/**
 *    Copyright 2017-2018 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package com.econage.eccm.test.base.mybatis.adaptation;

import com.econage.eccm.test.base.mybatis.MybatisCommonVar;
import com.econage.eccm.test.base.mybatis.MybatisException;
import com.econage.eccm.test.base.mybatis.cache.MybatisCacheAssistant;
import com.econage.eccm.test.base.mybatis.entity.*;
import com.econage.eccm.test.base.mybatis.enums.DBType;
import com.econage.eccm.test.base.mybatis.enums.FieldStrategy;
import com.econage.eccm.test.base.mybatis.enums.IdType;
import com.econage.eccm.test.base.mybatis.uid.dbincrementer.IKeyGenerator;
import com.econage.eccm.test.base.mybatis.mapper.BaseMapper;
import com.econage.eccm.test.base.mybatis.mapper.SqlInjector;
import com.econage.eccm.test.base.mybatis.util.*;
import com.google.common.base.Preconditions;
import com.google.common.reflect.ClassPath;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;

import java.io.Serializable;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListSet;

/**
 * <p>
 * Mybatis全局助手，维护额外的配置信息，提供若干辅助方法
 * </p>
 */
public class MybatisGlobalAssistant implements Serializable {
    private static final Log LOGGER = LogFactory.getLog(MybatisGlobalAssistant.class);

    //配置类，与助手类互相引用
    private final MybatisConfiguration configuration;
    // 数据库类型
    private DBType dbType;
    // 主键类型（默认 ID_WORKER）
    private IdType defaultIdType = IdType.ID_WORKER;

    // 列名称通用格式前缀
    private String dbColumnPrefix = StringUtils.EMPTY;
    // 列名称通用格式后缀
    private String dbColumnSuffix = StringUtils.UNDERLINE_STR;
    // 是否大写命名
    private boolean isCapitalMode = false;

    // SQL注入器
    private SqlInjector sqlInjector = new SqlInjector(this);
    // 表关键词 key 生成器
    private IKeyGenerator keyGenerator;
    // 字段验证策略
    private FieldStrategy fieldStrategy = FieldStrategy.NOT_NULL;
    // 标识符
    private String identifierQuote;
    // 缓存已注入CRUD的Mapper类信息
    private Set<String> mapperRegistryCache = new ConcurrentSkipListSet<String>();
    //两个角度维护表信息，一个通过mapper类，一个通过model类
    private final ConcurrentHashMap<Class<?>, TableInfo> mapperTableInfoMap = new ConcurrentHashMap<Class<?>, TableInfo>();
    private final ConcurrentHashMap<Class<?>, TableInfo> modelTableInfoMap = new ConcurrentHashMap<Class<?>, TableInfo>();

    private String[] packageNames;

    private final String frameBasePackage;

    //true时，mapper类必须是mapper结尾，并且直接包的名称是mapper
    private boolean isMapperNameValidate;

    private boolean globalCacheEnabled;

    public MybatisGlobalAssistant(MybatisConfiguration configuration) {
        this.configuration = configuration;
        this.frameBasePackage = MybatisCommonVar.class.getPackage().getName();
    }


    /*----------------------GETTER AND SETTER */
    public IKeyGenerator getKeyGenerator() {
        return keyGenerator;
    }

    public void setKeyGenerator(IKeyGenerator keyGenerator) {
        this.keyGenerator = keyGenerator;
    }

    public DBType getDbType() {
        return dbType;
    }

    /**
     * 根据jdbcUrl设置数据库类型
     *
     * @param jdbcUrl
     */
    public void setDbTypeOfJdbcUrl(String jdbcUrl) {
        this.dbType = JdbcUtils.getDbType(jdbcUrl);
    }

    public void setDbType(String dbType) {
        this.dbType = DBType.getDBType(dbType);
    }

    public void setDbType(DBType dbType) {
        this.dbType = dbType;
    }

    public IdType getDefaultIdType() {
        return defaultIdType;
    }

    public void setDefaultIdType(String defaultIdType) {
        this.defaultIdType = IdType.valueOf(defaultIdType);
    }

    public boolean isDbColumnUnderline() {
        return configuration.isMapUnderscoreToCamelCase();
    }

    public SqlInjector getSqlInjector() {
        return sqlInjector;
    }

    public void setSqlInjector(SqlInjector sqlInjector) {
        this.sqlInjector = sqlInjector;
    }

    public FieldStrategy getFieldStrategy() {
        return fieldStrategy;
    }

    public void setFieldStrategy(int fieldStrategy) {
        this.fieldStrategy = FieldStrategy.getFieldStrategy(fieldStrategy);
    }

    public boolean isCapitalMode() {
        return isCapitalMode;
    }

    public void setCapitalMode(boolean isCapitalMode) {
        this.isCapitalMode = isCapitalMode;
    }

    public String getDbColumnPrefix() {
        return dbColumnPrefix;
    }

    public void setDbColumnPrefix(String dbColumnPrefix) {
        this.dbColumnPrefix = dbColumnPrefix;
    }

    public String getDbColumnSuffix() {
        return dbColumnSuffix;
    }

    public void setDbColumnSuffix(String dbColumnSuffix) {
        this.dbColumnSuffix = dbColumnSuffix;
    }

    public MybatisConfiguration getConfiguration() {
        return configuration;
    }

    public String getIdentifierQuote() {
        if (null == identifierQuote) {
            return dbType.getQuote();
        }
        return identifierQuote;
    }
    public void setIdentifierQuote(String identifierQuote) {
        this.identifierQuote = identifierQuote;
    }

    public boolean isGlobalCacheEnabled() {
        return globalCacheEnabled;
    }

    public void setGlobalCacheEnabled(boolean globalCacheEnabled) {
        this.globalCacheEnabled = globalCacheEnabled;
    }

    private MybatisCacheAssistant mybatisCacheAssistant;

    public MybatisCacheAssistant getMybatisCacheAssistant() {
        return mybatisCacheAssistant;
    }

    public void setMybatisCacheAssistant(MybatisCacheAssistant mybatisCacheAssistant) {
        this.mybatisCacheAssistant = mybatisCacheAssistant;
    }
    /*----------------------GETTER AND SETTER----------- */

    public boolean isMapperCached(Class<?> mapper){
        if(!isMapperClass(mapper)){
            throw new IllegalArgumentException("it's not a mapper class.");
        }
        return mapperRegistryCache.contains(mapper.getName());
    }
    /*public void cacheMapperClass(Class<?> mapper){
        if(!isMapperClass(mapper)){
            throw new IllegalArgumentException("it's not a mapper class.");
        }
        mapperRegistryCache.add(mapper.getName());
    }*/

    //仅处理接口，如果继承了BaseMapper或者有Mapper注解，则认为是一个mapper对象
    public boolean isMapperClass(Class<?> resolvedCls){
        Preconditions.checkNotNull(resolvedCls,"mapper is null!");
        if(resolvedCls.isInterface()&&BaseMapper.class!=resolvedCls){
            if(BaseMapper.class.isAssignableFrom(resolvedCls)){
                return true;
            }else if(resolvedCls.getAnnotation(Mapper.class)!=null){
                return true;
            }
        }
        return false;
    }

    public void scanMapper(
            String... packageNames
    ){
        Preconditions.checkNotNull(packageNames,"PackageName is null!");
        if(this.packageNames!=null){
            throw new IllegalStateException("have scan package before!");
        }
        this.packageNames = packageNames;

        String resolvingClsName = null;
        try {
            ClassPath classpath = ClassPath.from(MybatisGlobalAssistant.class.getClassLoader());
            for (String packageName : packageNames) {
                for (ClassPath.ClassInfo classInfo : classpath.getTopLevelClassesRecursive(packageName)) {
                    resolvingClsName = classInfo.getName();
                    if(isMapperNameValid(resolvingClsName)){
                        Class<?> resolvedCls = Resources.classForName(resolvingClsName);
                        if (isMapperClass(resolvedCls)) {
                            if (LOGGER.isDebugEnabled()) {
                                LOGGER.debug("load mapper class :[" + resolvedCls.getName() + "]");
                            }
                            configuration.addMapper(resolvedCls);
                        }
                    }
                }
            }
        }catch(Throwable t){
            this.packageNames = null;
            LOGGER.error("error class:["+resolvingClsName+"]",t);
            throw new MybatisException("error class:["+resolvingClsName+"]",t);
        }
    }

    private boolean isMapperNameValid(String clazz){
        //true时，mapper类必须是mapper结尾，并且直接包的名称是mapper结尾
        clazz = clazz.toLowerCase();
        if(isMapperNameValidate){
            return clazz.endsWith("mapper")
                 &&clazz.substring(0,clazz.lastIndexOf(".")).endsWith("mapper");
        }else{
            return true;
        }
    }


    public void inspectInject4Mapper(MapperBuilderAssistant mapperBuilderAssistant, Class<?> mapper){
        sqlInjector.inspectInject(mapperBuilderAssistant,mapper);
    }

    public TableInfo saveAndGetTableInfoByMapper(Class<?> mapperClass) {
        if(mapperClass==null){
            return null;
        }
        if(mapperTableInfoMap.containsKey(mapperClass)){
            return mapperTableInfoMap.get(mapperClass);
        }
        Class<?> modelClass = ClassUtils.extractModelClass(mapperClass);
        if(modelClass==null){
            return null;
        }
        TableInfo tableInfo = saveAndGetTableInfoByModel(modelClass);
        if(tableInfo!=null){
            mapperTableInfoMap.putIfAbsent(mapperClass,tableInfo);
            mapperRegistryCache.add(mapperClass.getName());
            return mapperTableInfoMap.get(mapperClass);
        }
        return null;
    }

    public TableInfo saveAndGetTableInfoByModel(Class<?> modelClass){
        if(modelClass==null){
            return null;
        }
        if(modelTableInfoMap.containsKey(modelClass)){
            return modelTableInfoMap.get(modelClass);
        }
        if(!isClassInScanPackage(modelClass)){
            return null;
        }
        TableInfo tableInfo = TableInfoHelper.parseTableInfo(this,modelClass);
        modelTableInfoMap.putIfAbsent(modelClass,tableInfo);
        return modelTableInfoMap.get(modelClass);
    }

    //格式化没有手动备注的列名表名
    public String formatColumn(String property){
        Preconditions.checkNotNull(property,"column is null!");
        if(isDbColumnUnderline()){
            property = StringUtils.camelToUnderline(dbColumnPrefix,property,dbColumnSuffix);
        }
        if(isCapitalMode){
            property = property.toUpperCase();
        }
        return property;
    }

    public String formatTableName(String tableName){
        return formatColumn(tableName);
    }

    //部分列默认不参与更新，todo 改为可配置
    public boolean enableInDefaultUpdateMethod(String propertyName){
        if("createDate".equals(propertyName)||"createUser".equals(propertyName)){
            return false;
        }
        return true;
    }

    //处于业务程序的类，都可以视为有效的model类
    private boolean isClassInScanPackage(Class<?> clazz){
        Preconditions.checkNotNull(clazz,"class is null!");
        if(packageNames==null){
            throw new IllegalStateException("not scan package!");
        }
        String modelName = clazz.getName();
        for(String packageName:packageNames){
            //框架类，不参与扫描
            if(modelName.startsWith(packageName)
            &&!modelName.startsWith(frameBasePackage)){
                return true;
            }
        }
        return false;
    }

    public boolean isValidModel(Class<?> modelClass){
        return isClassInScanPackage(modelClass)
             ||modelTableInfoMap.contains(modelClass);
    }

    public boolean isMapperNameValidate() {
        return isMapperNameValidate;
    }

    public void setMapperNameValidate(boolean mapperNameValidate) {
        isMapperNameValidate = mapperNameValidate;
    }

}
